package sf.database.jdbc.rowmapper;

import sf.core.DBObject;
import sf.database.annotations.SmallResults;
import sf.database.jdbc.type.TypeHandler;
import sf.database.listener.EntityListenerManager;
import sf.database.meta.ColumnMapping;
import sf.database.meta.MetaHolder;
import sf.database.meta.TableMapping;
import sf.database.util.OrmValueUtils;
import sf.database.util.SQLUtils;
import sf.spring.util.LinkedCaseInsensitiveMap;
import sf.tools.StringUtils;

import javax.persistence.FieldResult;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Map;
import java.util.function.Function;

/**
 * 对象转换:
 * 对于子对象,需要使用@Results注解,设置子对象的属性和列名的对应.
 * @param <T>
 */
public class BeanRowMapper<T> implements RowMapper<T> {
    private Class<T> beanClass;
    private boolean useTail;//是否使用tail
    private boolean keepUpdate;//是否保留update字段值
    private Function<T, T> function;

    /**
     * @param beanClass
     */
    public BeanRowMapper(Class<T> beanClass) {
        this.beanClass = beanClass;
    }

    /**
     * @param beanClass  实体类
     * @param useTail    是否使用tail
     * @param keepUpdate 是否保留DBObject中的update字段值
     * @param function   回调方法
     */
    public BeanRowMapper(Class<T> beanClass, boolean useTail, boolean keepUpdate, Function<T, T> function) {
        this.beanClass = beanClass;
        this.useTail = useTail;
        this.keepUpdate = keepUpdate;
        this.function = function;
    }

    @Override
    public T handle(ResultSet rs, ResultSetMetaData rsmd, int rowNum) throws SQLException {
        try {
            TableMapping table = MetaHolder.getMeta(beanClass);
            T bean = OrmValueUtils.instance(table);
            SmallResults sqlResultSetMapping = beanClass.getAnnotation(SmallResults.class);
            boolean isDBObject = DBObject.class.isAssignableFrom(beanClass);
            if (isDBObject && !keepUpdate) {
                DBObject d = (DBObject) bean;
                //关闭动态插值
                d.stopUpdate();
            }
            int cols = rsmd.getColumnCount();
            for (int i = 1; i <= cols; i++) {
                String columnName = rsmd.getColumnLabel(i);
                if (StringUtils.isEmpty(columnName)) {
                    columnName = rsmd.getColumnName(i);
                }
                ColumnMapping cm = SQLUtils.getColumnByDBName(table, columnName);
                if (cm != null) {
                    TypeHandler<Object> handler = cm.getHandler();
                    Object value = handler.get(rs, i);
                    if (/*!cm.getClz().isPrimitive() || */value != null) {
                        //不为基础类型或者 值不为空,则插值
                        OrmValueUtils.setValue(bean, cm, value);
                    }
                    stackFind(rs, sqlResultSetMapping, bean, columnName);
                    continue;
                }

                boolean find = stackFind(rs, sqlResultSetMapping, bean, columnName);

                if (!find && useTail) {
                    //未找到将值设置在tail中.
                    if (isDBObject) {
                        DBObject d = (DBObject) bean;
                        Map<String, Object> map = d.getTail();
                        if (map == null) {
                            map = new LinkedCaseInsensitiveMap<>();
                            d.setTail(map);
                        }
                        map.put(columnName, rs.getObject(columnName));
                    } else if (table.getTailField() != null) {
                        Map<String, Object> map = OrmValueUtils.getValue(bean, table.getTailField());
                        if (map == null) {
                            map = new LinkedCaseInsensitiveMap<>();
                            OrmValueUtils.setValue(bean, table.getTailField(), map);
                        }
                        map.put(columnName, rs.getObject(columnName));
                    }
                }
            }
            if (isDBObject && !keepUpdate) {
                DBObject d = (DBObject) bean;
//                d.clearUpdate();
                //开启动态插值
                d.startUpdate();
            }
            if (function != null) {
                bean = function.apply(bean);
            }
            EntityListenerManager.runPostLoad(bean);
            return bean;
        } catch (Exception e) {
            throw new SQLException("Can't set bean property.", e);
        }
    }

    /**
     * 迭代查找赋值
     * @param rs
     * @param bean
     * @param columnName
     * @return
     * @throws SQLException
     */
    private boolean stackFind(ResultSet rs, SmallResults sqlResultSetMapping, Object bean, String columnName)
            throws SQLException {
        boolean flag = false;
        if (sqlResultSetMapping != null && sqlResultSetMapping.value().length > 0) {
            FieldResult[] fields = sqlResultSetMapping.value();
            for (int i = 0; i < fields.length; i++) {
                FieldResult fieldResult = fields[i];
                String name = fieldResult.name();
                if (columnName.equals(fieldResult.column())) {
                    String[] fieldNames = StringUtils.split(name, ".");
                    flag = setSubProperties(bean, fieldNames, 0, rs, columnName);
                }
            }
        }
        return flag;
    }

    private boolean setSubProperties(Object bean, String[] fieldPaths, int i, ResultSet rs, String columnName) throws SQLException {
        boolean flag = false;
        String fieldName = fieldPaths[i];
        TableMapping table = MetaHolder.getMeta(bean.getClass());
        ColumnMapping cm = table.getJavaFieldColumnMapping(fieldName);
        if (cm != null) {
            if (fieldPaths.length == 1 || i == fieldPaths.length - 1) {
                TypeHandler<Object> handler = cm.getHandler();
                Object value = handler.get(rs, columnName);
                if (value != null) {
                    OrmValueUtils.setValue(bean, cm, value);
                }
                flag = true;
            } else {
                Object obj = null;
                Object innerObj = OrmValueUtils.getValue(bean, cm);
                if (innerObj == null) {
                    obj = OrmValueUtils.instance(MetaHolder.getMeta(cm.getClz()));
                } else {
                    obj = innerObj;
                }
                boolean isDBObject = DBObject.class.isAssignableFrom(cm.getClz());
                if (isDBObject) {
                    DBObject d = (DBObject) obj;
                    //关闭动态插值
                    d.stopUpdate();
                }
                flag = setSubProperties(obj, fieldPaths, i + 1, rs, columnName);
                // 使用set字段方法,非method,可能对某些特殊方法执行不到
                if (innerObj == null) {
                    OrmValueUtils.setValue(bean, cm, obj);
                }
                //清除updateValue
                if (isDBObject) {
                    DBObject d = (DBObject) obj;
                    //开启动态插值
                    d.startUpdate();
                }
            }
        }
        return flag;
    }

}
